﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NeuralNetworks.Layers;
using NeuralNetworks.ActivationFunctions;
using NeuralNetworks.Learning;
using System.ComponentModel;
using NeuralNetworks.Neurons;

namespace NeuralNetworks.Networks
{
    class CounterPropagationNetwork : SomNetwork
    {
        private ActivationNetwork _grossbergLayer;
        private DeltaRuleLearning _learningRule;

        private List<List<int>> _pClasses; //trzyma indeksy wzorcow dla danej klasy (pierwszy wymiar klasa, drugi indeksy wzorcow)
        private double[][] _outputs;

        public CounterPropagationNetwork(int nPatterns, List<List<int>> pClasses, string[] patterns, int kohXSize, int kohYSize, IActivationFunction grossbergActv) :
            base(nPatterns, patterns, kohXSize, kohYSize)
        {
            //_grossbergLayer = new ActivationLayer(pClasses.Count, grossbergActv, "GrossbergLeyer");
            //_grossbergLayer.ConnectWithLayer(this.KohonenLayer);
            _grossbergLayer = new ActivationNetwork(kohXSize * kohYSize, 0, pClasses.Count, new int[]{}, grossbergActv);
            _learningRule = new DeltaRuleLearning(_grossbergLayer);
            _pClasses = pClasses;
            _outputs = new double[_patterns.Length][];
            int j = 0;
            int nClasses = _pClasses.Count;
            for (int i = 0; i < nClasses; i++)
            {
                foreach (int index in _pClasses[i])
                {
                    _outputs[index] = new double[nClasses];
                    _outputs[index][i] = 1.0;
                }
            }
        }

        override public double[] Compute(double[] input)
        {

            //return base.Compute(input);
            
            this.InputLayer.SetOutputs(input);

            this.KohonenLayer.Compute();
            _grossbergLayer.Compute(this.KohonenLayer.Outputs());
            
            return _grossbergLayer.outputLayer.Outputs();
        }

        override public void Learn(SortedList<int, LearningProperty> property, int epochs)
        {
            base.Learn(property, epochs);
        }

        public double LearnOut(SortedList<int, LearningProperty> property, int epochs)
        {

            foreach (Neuron n in _grossbergLayer.outputLayer.Neurons)
            {
                n.BiasWeight = 0.0;
            }
            LearningProperty currentProp = getCurrentProperty(property);
            _learningRule.LearningRate = currentProp.LearningRatio;

            double[][] inputs = new double[_patterns.Length][];
            for (int i = 0; i < _patterns.Length; i++)
            {
                inputs[i] = new double[_patterns[i].Length];
                this.Compute(_patterns[i]);
                double[] outputs = this.KohonenLayer.Outputs();
                for (int j = 0; j < outputs.Length; j++)
                {
                    inputs[i][j] = outputs[j];
                }
            }

            double error = 10000; ;
            for (int i = 0; i < epochs; i++)
            {
                error = _learningRule.RunEpoch(inputs, _outputs);
                //Console.WriteLine(Math.Round(error,4));
                _epochs++;
                int pIndex = property.IndexOfKey(_epochs);
                if (pIndex > -1) //trzeba przelaczyc sie na nowe parametry
                {
                    try
                    {
                        currentProp = property[property.Keys[pIndex + 1]];
                        Console.WriteLine(currentProp);
                        _learningRule.LearningRate = currentProp.LearningRatio;
                    }
                    catch (ArgumentOutOfRangeException e)
                    {
                    }
                }
            }
            return error;
        }
        public override void RandomWeights(double min, double max)
        {
            base.RandomWeights(min, max);
            _grossbergLayer.RandomWeights(min, max);
        }
    }
}
